/***************************************************************************
 *   Copyright (C) 2007 by Autostop team                                   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
///@file gnetserver.cpp Base class for TCP server

// Qt
#include <QtGlobal>

// gcommon
#include "gexception.h"

// local
#include "gnettypes.h"
#include "gnetpackets.h"
#include "gnetserver.h"

// --------------------------- constructor -----------------------------------
GNetServer::GNetServer( QObject* pParent /* =NULL*/ ) 
	: QObject( pParent ), _server( this )
{
	_pSocket = NULL;
	_timeout = 10000; // 10 sec
	
	_authorized = false;
	
	_pSocket = new QTcpSocket();
}

// --------------------------- destructor ------------------------------------
GNetServer::~GNetServer()
{
	// nothing
}

// ---------------------- GNetServer::setSocket ------------------------------
void GNetServer::setSocket( QTcpSocket * pSocket )
{
	qDebug("GNetServer::setSocket( %p )", pSocket );
	disconnectFromHost(); // just in case
	
	_pSocket = pSocket;
	
	processAuthorization();
}
// ---------------------------------------------------------------------------


// -------------------- GNetServer::setSocketDescriptor() --------------------
void GNetServer::setSocketDescriptor( int socketDescriptor,
									  QAbstractSocket::SocketState socketState,
									  QIODevice::OpenMode openMode
									)
{
	if( !_pSocket )
	{
		_pSocket = new QTcpSocket();
	}
	
	if( _pSocket )
	{
		_pSocket->setSocketDescriptor( socketDescriptor,
									   socketState,
									   openMode
									 );
		processAuthorization();
	}
}
// ---------------------------------------------------------------------------


// --------------------------- listen ----------------------------------------
/// Listens synchronously for connection on specified port/address.
/// When connection arrives, it is accepted and used for further communication
void GNetServer::listen( quint16 port, const QHostAddress & address /*= QHostAddress::Any*/ )
{
	// disconnect current connection
	disconnectFromHost();

	
	// listen
	if ( ! _server.listen( address, port ) )
	{
		throw GSysError( _server.errorString() );
	}

	// wait for new connection
	if ( ! _server.waitForNewConnection(-1) )
	{
		throw GSysError( _server.errorString() );
	}
	
	// accept connection
	QTcpSocket * pNewConnection = _server.nextPendingConnection();
	if ( ! pNewConnection )
	{
		throw GSysError( tr("No incoming conection" ) );
	}
	
	setSocket( pNewConnection );
	
		// check for valid password 
	processAuthorization();
}


// --------------------------- isConnected -----------------------------------
bool GNetServer::isConnected() const
{
	if ( _pSocket )
	{
		return  _pSocket->state() == QAbstractSocket::ConnectedState;
		// NOTE thiswas com,mented and changed to '_pSocket != NULL'. Why?
	}
	
	return false;
}


// --------------------------- disconnectFromHost ----------------------------
void GNetServer::disconnectFromHost()
{
	if ( _pSocket )
	{
		if( isConnected() )
		{
			_pSocket->disconnectFromHost();
			if ( ! _pSocket->waitForDisconnected( _timeout ) )
			{
				throw GTimeoutError( _pSocket->errorString() );
			}
		}
	}
}


// --------------------------- processPackets --------------------------------
/// Starts packet processing, does not return.
/// Throws configuration exception if network connection is not
/// Established. Throws other exceptions on communication errors
void GNetServer::processPackets()
{
	// check config
	if ( ! isConnected() )
		throw GConfigurationError( tr("No connection established") );

	if( ! _authorized )
		throw GConfigurationError( tr("No authentication.") );
		
	
	// process packets here
	forever
	{
		GNetPacket * pPacket = waitForPacket();
		
		if ( pPacket )
		{
            processPacket( pPacket );
            delete pPacket;
		}
		else
		{
			break;
		}
	}
	
}


// --------------------------- waitForPacket ---------------------------------
/// Waits for incomng packet
GNetPacket* GNetServer::waitForPacket()
{
	if ( isConnected() )
	{
		GHeader header;
		
		// read header
		header.readFromSocket( _pSocket, -1 );
		//qDebug() << "HEADER OK "; // TODO remove debug
		// create apropriate packet
		GNetPacket * pPacket = GNetPacketFactory::instance()->createPacket( header );
		// read packet
		if ( pPacket )
		{
			pPacket->readFromSocket( _pSocket, _timeout );
			
			return pPacket;
		}
		
	}

	return NULL;
}


// --------------------------- sendPacket ------------------------------------
/// Sends packet
void GNetServer::sendPacket( const GNetPacket & packet )
{
	if ( isConnected() )
	{
		GHeader header = packet.header();

		header.writeToSocket( _pSocket, _timeout );
		packet.writeToSocket( _pSocket, _timeout );
		
	}
}



// ============================== processAuthorization =======================
void GNetServer::processAuthorization()
{
	qDebug("GNetServer::processAuthorization()"); // TODO remove debug
	setAuthorized( false );
	
	// create answer packet
	GNetPacketAuthorizationAnswer * pAuthorizationAnswer = new GNetPacketAuthorizationAnswer();
		// cannot create answer packet
	if( !pAuthorizationAnswer ) return; // TODO throw here?
	
	GNetPacket * pPacket = waitForPacket();
	
	if( pPacket->packetType() == GNetPacket::GNETPACKET_AUTHORIZATION )
	{
		GNetPacketAuthorization * pPacketAuthorization;
		pPacketAuthorization = qobject_cast<GNetPacketAuthorization *>( pPacket );
		
		if( pPacketAuthorization )
		{
			QString login    = pPacketAuthorization->login();
			QString password = pPacketAuthorization->password();
			qDebug() << "loging" << login << password;
			switch( passUser( login, password ) )
			{
				case GNetPacketAuthorizationAnswer::STATUS_OK :
				{
					setAuthorized( true );
						// send answer
					pAuthorizationAnswer->setStatus( GNetPacketAuthorizationAnswer::STATUS_OK );
					sendPacket( *pAuthorizationAnswer );
				}
				break;
				
				case GNetPacketAuthorizationAnswer::STATUS_LOGIN_INCORRECT :
				{
					// send answer
					pAuthorizationAnswer->setStatus( GNetPacketAuthorizationAnswer::STATUS_LOGIN_INCORRECT );
					pAuthorizationAnswer->setMessage( tr("Invalid user name.") );
					sendPacket( *pAuthorizationAnswer );
					disconnectFromHost();
				}
				break;
				
				case GNetPacketAuthorizationAnswer::STATUS_PASSWORD_INCORRECT :
				{
					// send answer
					pAuthorizationAnswer->setStatus( GNetPacketAuthorizationAnswer::STATUS_PASSWORD_INCORRECT );
					pAuthorizationAnswer->setMessage( tr("Invalid password.") );
					sendPacket( *pAuthorizationAnswer );
					disconnectFromHost();
				}
				break;

				default:
					break;
					
			} // !switch( passUser( login, password ) )
			
		} // !if( pPacketAuthorization )
		else
		{
			qDebug( "GNetServer::processAuthorization() : packet casting error" );
		}
		
	} // !if( pPacket->packetType() )
	else
	{
		qDebug( "GNetServer::processAuthorization() : wrong packet type" );
		qDebug() << "type" << (pPacket->packetType() - MKWORD( 'a', 'u', 't', 'o' ) );
	}
}


// eof


